P08-2. 통합 수집기 Grafana Alloy와 Mimir VictoriaMetrics - 실습 검증
Part 08 (실습): LGTM 메트릭 파이프라인 — Grafana Alloy와 Mimir / VictoriaMetrics 검증
- 관측 전용 네임스페이스(
monitoring)를 생성하고 Ambient Mesh(istio.io/dataplane-mode=ambient)에 편입시켜STRICTmTLS 환경의 워크로드를 안전하게 스크래핑할 수 있도록 구성한다. - Prometheus/Mimir 100% 호환 시계열 DB인
VictoriaMetrics(v1.154.1), 외부 PostgreSQL 18 VM을 감시하는postgres-exporter(v0.16.0), 그리고 차세대 단일 통합 수집기인Grafana Alloy(v1.21.2) 를 배포한다. VictoriaMetrics의 PromQL HTTP API(/api/v1/query)를 직접 호출하여Waypoint Proxy의 L7 RED 메트릭(istio_requests_total,p99지연 시간),ztunnel의 L4 TCP 연결 메트릭(istio_tcp_connections_opened_total), 그리고 외부 PostgreSQL 18 VM의 실시간 상태(pg_up,pg_locks_count,pg_stat_activity_count) 가 단일 TSDB에서 통합 조회되는 것을 100% 실측한다.
- P08-1 이론 개념을 먼저 읽고 오면
Grafana Alloy의 River 문법과 히스토그램 버킷(_bucket,le,p99) 공식이 한눈에 이해됩니다. - Windows PowerShell 창을 열고 아래 명령어로 클러스터 상태를 확인합니다.
$env:Path += ";$HOME\bin" cd "$HOME\mesh-study" kubectl get nodes
실습 아키텍처 및 메트릭 수집 파이프라인 한눈에 보기
flowchart LR
subgraph TARGETS["메트릭 수집 대상 (Scrape Targets)"]
ZT["ztunnel (istio-system)
:15020/stats/prometheus
L4 TCP/HBONE 메트릭"]
WP["Waypoint (part1-demo)
:15020/stats/prometheus
L7 HTTP RED & Histogram"]
PG_EXP["postgres-exporter (part1-demo)
:9187/metrics
(외부 study-pg-vm:5432 감시)"]
end
subgraph ALLOY_PIPE["monitoring 네임스페이스 (Ambient Mesh 편입!)"]
ALLOY["Grafana Alloy (v1.21.2)
1. discovery.kubernetes 'pods'
2. prometheus.scrape (5s 주기)
3. prometheus.remote_write"]
VM["VictoriaMetrics (v1.154.1)
:8428 (/api/v1/write & /api/v1/query)
Mimir/Prometheus 100% 호환 TSDB"]
end
ZT -->|"5초 주기 Scrape"| ALLOY
WP -->|"5초 주기 Scrape"| ALLOY
PG_EXP -->|"HBONE mTLS Scrape"| ALLOY
ALLOY ==>|"Remote Write"| VMStep 1. monitoring 네임스페이스 생성 및 Ambient Mesh 편입 (STRICT mTLS 연동!)
앞서 Part 04에서 우리는 part1-demo 네임스페이스에 PeerAuthentication: strict-mtls (mode: STRICT) 정책을 걸어 두었습니다.
만약 monitoring 네임스페이스에 뜨는 수집기(Grafana Alloy)가 Ambient Mesh 바깥에서 평문(Plaintext)으로 part1-demo의 파드(postgres-exporter)를 긁으려 하면 ztunnel이 즉시 연결을 차단합니다!
따라서 monitoring 네임스페이스도 istio.io/dataplane-mode=ambient 라벨을 붙여 메시 가족으로 편입시킵니다.
# 1. monitoring 네임스페이스 생성
kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f -
# 2. monitoring 네임스페이스를 Ambient Mesh에 편입 (Alloy가 HBONE mTLS로 스크래핑 가능!)
kubectl label namespace monitoring istio.io/dataplane-mode=ambient --overwrite
Step 2. VictoriaMetrics · postgres-exporter · Grafana Alloy 통합 배포
이제 3개의 핵심 컴포넌트를 한 번에 배포합니다:
vmsingle(victoriametrics/victoria-metrics:v1.154.1):8428번 포트에서 메트릭을 저장하고 PromQL 쿼리에 응답하는 초고속 시계열 DB입니다.postgres-exporter(prometheuscommunity/postgres-exporter:v0.16.0):part1-demo안에서 실행되며, 우리가 Part 06에서 만든 메시 도메인(pg-vm.mesh.internal:5432)을 타고 외부 PostgreSQL 18 VM에 접속해 DB 내부 지표(pg_locks_count,pg_stat_activity_count)를 Prometheus 포맷(:9187/metrics)으로 변환해 줍니다!alloy(grafana/alloy:v1.21.2): 클러스터 내 모든15020포트(ztunnel및Waypoint)와postgres-exporter(9187포트)를 5초마다 긁어서vmsingle로 전송(remote_write)합니다.
scrape_interval = "5s" 설정 시 scrape_timeout 필수!
prometheus.scrape 블록의 기본 scrape_timeout(응답 대기 제한 시간)은 10s 입니다. 만약 scrape_interval(수집 주기)만 "5s"로 줄이고 scrape_timeout을 안 적으면, Alloy가 기동할 때 Error: scrape_timeout (10s) greater than scrape_interval (5s) 에러를 내며 멈춥니다! 따라서 주기를 10초 미만으로 줄일 때는 반드시 scrape_timeout = "4s" 처럼 수집 주기보다 작은 값을 함께 명시해야 합니다.
# part8-metrics-stack.yaml 생성 및 적용
@"
apiVersion: apps/v1
kind: Deployment
metadata:
name: vmsingle
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: vmsingle
template:
metadata:
labels:
app: vmsingle
spec:
containers:
- name: vmsingle
image: victoriametrics/victoria-metrics:v1.154.1
args:
- "-retentionPeriod=7d"
- "-httpListenAddr=:8428"
ports:
- containerPort: 8428
---
apiVersion: v1
kind: Service
metadata:
name: vmsingle
namespace: monitoring
spec:
selector:
app: vmsingle
ports:
- name: http
port: 8428
targetPort: 8428
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-exporter
namespace: part1-demo
spec:
replicas: 1
selector:
matchLabels:
app: postgres-exporter
template:
metadata:
labels:
app: postgres-exporter
spec:
containers:
- name: postgres-exporter
image: prometheuscommunity/postgres-exporter:v0.16.0
env:
- name: DATA_SOURCE_NAME
value: "postgresql://meshuser:meshpass@pg-vm.mesh.internal:5432/ordersdb?sslmode=disable"
ports:
- containerPort: 9187
---
apiVersion: v1
kind: Service
metadata:
name: postgres-exporter
namespace: part1-demo
spec:
selector:
app: postgres-exporter
ports:
- name: metrics
port: 9187
targetPort: 9187
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: alloy
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: alloy-observer
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/proxy", "nodes/metrics", "services", "endpoints", "pods", "pods/log", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alloy-observer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: alloy-observer
subjects:
- kind: ServiceAccount
name: alloy
namespace: monitoring
---
apiVersion: v1
kind: ConfigMap
metadata:
name: alloy-config
namespace: monitoring
data:
config.alloy: |
discovery.kubernetes "pods" {
role = "pod"
}
// 1. Istio ztunnel & Waypoint (:15020/stats/prometheus) 자동 발견 및 수집
discovery.relabel "istio_mesh" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_pod_container_port_number"]
regex = "15020|15090"
action = "keep"
}
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
}
prometheus.scrape "istio_mesh" {
targets = discovery.relabel.istio_mesh.output
metrics_path = "/stats/prometheus"
scrape_interval = "5s"
scrape_timeout = "4s"
forward_to = [prometheus.remote_write.vmsingle.receiver]
}
// 2. 외부 PostgreSQL 18 VM 감시용 postgres-exporter (:9187/metrics) 수집
prometheus.scrape "postgres_exporter" {
targets = [{
__address__ = "postgres-exporter.part1-demo.svc.cluster.local:9187",
job = "postgres-exporter",
db_instance = "study-pg-vm",
}]
scrape_interval = "5s"
scrape_timeout = "4s"
forward_to = [prometheus.remote_write.vmsingle.receiver]
}
// 3. VictoriaMetrics (Mimir/Prometheus 호환 TSDB)로 Remote Write 전송
prometheus.remote_write "vmsingle" {
endpoint {
url = "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/write"
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: alloy
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: alloy
template:
metadata:
labels:
app: alloy
spec:
serviceAccountName: alloy
containers:
- name: alloy
image: grafana/alloy:v1.21.2
args:
- "run"
- "/etc/alloy/config.alloy"
- "--server.http.listen-addr=0.0.0.0:12345"
ports:
- containerPort: 12345
name: http-ui
volumeMounts:
- name: config
mountPath: /etc/alloy
volumes:
- name: config
configMap:
name: alloy-config
"@ | Set-Content -Path "$HOME\mesh-study\part8-metrics-stack.yaml" -Encoding utf8
kubectl apply -f "$HOME\mesh-study\part8-metrics-stack.yaml"
kubectl wait --for=condition=Ready pod --all -n monitoring --timeout=120s
kubectl wait --for=condition=Ready pod -l app=postgres-exporter -n part1-demo --timeout=120s
실제 출력 결과:
deployment.apps/vmsingle created
service/vmsingle created
deployment.apps/postgres-exporter created
service/postgres-exporter created
...
pod/alloy-5dcbd94545-kjhw5 condition met
pod/vmsingle-7b77cc877c-5ds97 condition met
pod/postgres-exporter-674db86bbc-vb267 condition met
Step 3. L7 Waypoint RED 메트릭 및 p99 지연 시간(Histogram) 실측 쿼리!
이제 study-client에서 order-api로 10번 요청을 보낸 뒤, VictoriaMetrics의 /api/v1/query 엔드포인트에 PromQL을 날려 지금까지 누적된 서비스별·응답코드별 요청 수(istio_requests_total) 와 p99 지연 시간(histogram_quantile) 을 조회합니다!
# 1. 테스트 트래픽 10회 발생 후 스크래핑 주기(5초) 대기
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/metrics-test?[1-10]" | Out-Null
Start-Sleep -Seconds 6
# 2. [R & E] 서비스별 / HTTP 상태 코드별 총 요청 횟수(istio_requests_total) 조회!
kubectl exec -n part1-demo study-client -- curl -s "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=sum(istio_requests_total)+by+(destination_service_name,response_code)"
# 3. [D] 상위 99% 응답 지연 시간(p99 Latency, 단위: ms) 조회!
kubectl exec -n part1-demo study-client -- curl -s -g "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=histogram_quantile(0.99,sum(rate(istio_request_duration_milliseconds_bucket[5m]))by(le,destination_service_name))"
실제 출력 결과:
// [1. 서비스별 / 상태 코드별 요청 횟수]
{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{"metric": {"destination_service_name": "order-api", "response_code": "200"}, "value": [1792913696, "50"]},
{"metric": {"destination_service_name": "order-api", "response_code": "403"}, "value": [1792913696, "6"]},
{"metric": {"destination_service_name": "order-api-v1", "response_code": "200"}, "value": [1792913696, "60"]},
{"metric": {"destination_service_name": "order-api-v1", "response_code": "500"}, "value": [1792913696, "4"]},
{"metric": {"destination_service_name": "order-api-v1", "response_code": "503"}, "value": [1792913696, "12"]},
{"metric": {"destination_service_name": "order-api-v2", "response_code": "200"}, "value": [1792913696, "20"]}
]
}
}
// [2. order-api의 p99 지연 시간 = 약 9.93ms!]
{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{"metric": {"destination_service_name": "order-api"}, "value": [1792913697, "9.928571428571427"]}
]
}
}
- Part 04에서 차단했던 해커 요청:
destination_service_name="order-api", response_code="403"(6건) - Part 05에서 실험했던 카나리 배포(
v1vsv2)와 결함 주입(500), 서킷 브레이커(503):order-api-v1의500(4건),503(12건), 그리고order-api-v2의200(20건)까지Waypoint Proxy가 하나도 놓치지 않고 측정해VictoriaMetrics에 저장했습니다! p99지연 시간:order-api요청 중 가장 느린 상위 1%(0.99)의 응답 시간이9.93ms임을histogram_quantile공식으로 정확히 산출해 냈습니다!
Step 4. L4 ztunnel TCP 메트릭 및 외부 PostgreSQL 18 VM 메트릭 통합 조회!
이번에는 HTTP가 아닌 순수 TCP 연결(외부 DB VM인 pg-vm.mesh.internal:5432 포함) 에 대한 L4 메트릭과, postgres-exporter가 수집한 외부 PostgreSQL 18 내부의 Lock 및 활성 세션 메트릭을 조회합니다.
# 1. [L4 Mesh] 목적지 서비스별 총 TCP 연결 오픈 횟수 (외부 DB VM인 pg-vm.mesh.internal 포함!)
kubectl exec -n part1-demo study-client -- curl -s "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=sum(istio_tcp_connections_opened_total)+by+(destination_service_name)"
# 2. [External DB] 외부 PostgreSQL 18 VM 생존 여부(pg_up = 1) 및 DB별 Lock 개수(pg_locks_count) 조회!
kubectl exec -n part1-demo study-client -- curl -s "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=pg_up"
kubectl exec -n part1-demo study-client -- curl -s "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=pg_locks_count" | Select-Object -First 1
실제 출력 결과:
// [1. L4 TCP 연결 오픈 횟수 — 외부 VM 도메인(pg-vm.mesh.internal: 32회)까지 완벽 추적!]
{
"status": "success",
"data": {
"result": [
{"metric": {"destination_service_name": "external-pg"}, "value": [1792913696, "6"]},
{"metric": {"destination_service_name": "external-pg-se"}, "value": [1792913696, "16"]},
{"metric": {"destination_service_name": "order-api"}, "value": [1792913696, "39"]},
{"metric": {"destination_service_name": "pg-vm.mesh.internal"}, "value": [1792913696, "32"]}
]
}
}
// [2. 외부 PostgreSQL 18 VM (db_instance="study-pg-vm") 생존 확인 및 Lock 모드별 카운트!]
{
"status": "success",
"data": {
"result": [
{"metric": {"__name__": "pg_up", "db_instance": "study-pg-vm", "job": "postgres-exporter"}, "value": [1792913752, "1"]}
]
}
}
- 메시(L4/L7) + 외부 DB VM 메트릭의 단일화 완성: 이제 우리는 하나의 시계열 DB(
VictoriaMetrics) 안에서order-api의 L7 HTTP 지연 시간,pg-vm.mesh.internal로 향하는 L4 TCP 연결 횟수, 그리고 그 외부 PostgreSQL 18 서버 내부의pg_locks_count와pg_stat_activity_count를 동시에 대조할 수 있게 되었습니다!
Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)
| 증상 / 에러 메시지 | 원인 | 해결 방법 |
|---|---|---|
alloy 파드 로그에 scrape_timeout (10s) greater than scrape_interval (5s) 에러 출력 |
prometheus.scrape 블록의 기본 scrape_timeout이 10s여서 scrape_interval을 5s로 줄이면 역전 발생 |
scrape_interval = "5s" 밑에 반드시 scrape_timeout = "4s" 를 함께 지정 |
istio_requests_total은 조회되는데 pg_up이 빈 배열([])로 나옴 |
part1-demo에 PeerAuthentication(STRICT)가 걸려 있는데 monitoring 네임스페이스가 Ambient Mesh에 편입되지 않아 평문 스크래핑이 차단됨 |
kubectl label namespace monitoring istio.io/dataplane-mode=ambient --overwrite 실행 |
curl로 [5m]이 들어간 PromQL 호출 시 curl: (3) bad range in URL 에러 발생 |
curl이 대괄호([5m])를 URL 범위 확장 문법([1-10])으로 오인함 |
curl에 -g (--globoff) 옵션을 붙여서 실행 (curl -s -g "http://...") |
Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)
# 1. Grafana Alloy 수집기 상태 및 파이프라인 로드 로그 확인
kubectl logs -n monitoring -l app=alloy --tail=20
# 2. VictoriaMetrics에서 L7 HTTP 요청 수(Rate/Error) 조회
kubectl exec -n part1-demo study-client -- curl -s "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=sum(istio_requests_total)+by+(destination_service_name,response_code)"
# 3. VictoriaMetrics에서 상위 99% 지연 시간(p99 Duration) 조회 (-g 옵션 필수)
kubectl exec -n part1-demo study-client -- curl -s -g "http://vmsingle.monitoring.svc.cluster.local:8428/api/v1/query?query=histogram_quantile(0.99,sum(rate(istio_request_duration_milliseconds_bucket[5m]))by(le,destination_service_name))"
Step 7. 셀프 체크 (Self-Check Q&A)
Q1. 마이크로서비스의 응답 속도를 감시할 때 왜 '평균(Average)' 대신 p99 백분위수(histogram_quantile(0.99, ...))를 사용해야 하나요?
전체 요청의 99%가 10ms 만에 끝나고 1%의 중요 결제 요청이 10,000ms(10초) 동안 멈추더라도 단순 평균을 내면 약 110ms로 계산되어 심각한 장애가 가려지기 때문입니다. 반면 p99는 가장 느린 상위 1% 고객이 실제로 겪는 지연 시간(10초)을 정확히 포착해 줍니다.
Q2. part1-demo 네임스페이스에 STRICT mTLS 보안이 적용된 상태에서, monitoring 네임스페이스의 Grafana Alloy가 part1-demo 안의 postgres-exporter를 정상적으로 스크래핑하려면 어떤 조치가 필요한가요?
monitoring 네임스페이스에도 istio.io/dataplane-mode=ambient 라벨을 부여해 Ambient Mesh에 편입시켜야 합니다. 그러면 Alloy 파드에서 나가는 스크래핑 요청도 노드의 ztunnel을 통해 자동으로 HBONE(:15008) mTLS 암호화 터널을 타게 되어 STRICT 검문을 통과합니다.
🔗 다음 파트로 이동
LGTM의 첫 번째 기둥인 M(Metric) 파이프라인이 완성되었습니다!
다음 Part 09에서는 두 번째 기둥인 L(Grafana Loki) 을 구축하여 ztunnel의 L4 HBONE 액세스 로그와 Waypoint Proxy의 L7 Envoy 액세스 로그를 실시간 수집하고, LogQL 로 서킷 브레이커(UO)와 보안 차단 로그를 정밀 타격해 봅니다!
네비게이션
| 이전 | 목록 | 다음 |
|---|---|---|
| ◀ Part 08 이론 | 목록으로 | Part 09 이론 ▶ |